package com.bitejiuyeke.biteadminservice.house.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bitejiuyeke.biteadminapi.config.domain.dto.DictionaryDataDTO;
import com.bitejiuyeke.biteadminapi.house.domain.dto.DeviceDTO;
import com.bitejiuyeke.biteadminapi.house.domain.dto.SearchHouseListReqDTO;
import com.bitejiuyeke.biteadminapi.house.domain.dto.TagDTO;
import com.bitejiuyeke.biteadminservice.config.service.ISysDictionaryService;
import com.bitejiuyeke.biteadminservice.house.domain.dto.*;
import com.bitejiuyeke.biteadminservice.house.domain.entity.*;
import com.bitejiuyeke.biteadminservice.house.domain.enums.HouseSortEnum;
import com.bitejiuyeke.biteadminservice.house.domain.enums.HouseStatusEnum;
import com.bitejiuyeke.biteadminservice.house.mapper.*;
import com.bitejiuyeke.biteadminservice.house.service.IHouseService;
import com.bitejiuyeke.biteadminservice.house.service.filter.IHouseFilter;
import com.bitejiuyeke.biteadminservice.house.service.strategy.ISortStrategy;
import com.bitejiuyeke.biteadminservice.house.service.strategy.SortStrategyFactory;
import com.bitejiuyeke.biteadminservice.map.domain.entity.SysRegion;
import com.bitejiuyeke.biteadminservice.map.mapper.RegionMapper;
import com.bitejiuyeke.biteadminservice.user.domain.entity.AppUser;
import com.bitejiuyeke.biteadminservice.user.mapper.AppUserMapper;
import com.bitejiuyeke.bitecommoncore.domain.dto.BasePageDTO;
import com.bitejiuyeke.bitecommoncore.utils.BeanCopyUtil;
import com.bitejiuyeke.bitecommoncore.utils.JsonUtil;
import com.bitejiuyeke.bitecommoncore.utils.TimestampUtil;
import com.bitejiuyeke.bitecommondomain.domain.ResultCode;
import com.bitejiuyeke.bitecommondomain.exception.ServiceException;
import com.bitejiuyeke.bitecommonredis.service.RedisService;
import com.bitejiuyeke.bitecommonredis.service.RedissonLockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
@RefreshScope
public class IHouseServiceImpl implements IHouseService {

    //城市房源映射key前缀
    private static final String CITY_HOUSE_PREFIX = "house:list:";
    //城市完整信息key前缀
    private static final String HOUSE_PREFIX = "house:";
    private static final String LOCK_KEY= "scheduledTask:lock";

    @Autowired
    private final Map<String, IHouseFilter> houseFilterMap = new HashMap<>();

    @Autowired
    private HouseMapper houseMapper;

    @Autowired
    private HouseStatusMapper houseStatusMapper;

    @Autowired
    private TagHouseMapper tagHouseMapper;

    @Autowired
    private CityHouseMapper cityHouseMapper;

    @Autowired
    private TagMapper tagMapper;

    @Autowired
    private RedisService redisService;

    @Autowired
    private AppUserMapper appUserMapper;

    @Autowired
    private ISysDictionaryService sysDictionaryService;

    @Autowired
    private RedissonLockService redissonLockService;

    @Autowired
    private RegionMapper regionMapper;

    /**
     * 新增或编辑房源
     *
     * @param houseAddOrEditReqDTO 新增或编辑房源参数
     * @return 新增或编辑房源ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addOrEdit(HouseAddOrEditReqDTO houseAddOrEditReqDTO) {
        //校验参数
        checkAddOrEditReq(houseAddOrEditReqDTO);

        //设置参数信息
        House house= new House();
        house.setUserId(houseAddOrEditReqDTO.getUserId());
        house.setTitle(houseAddOrEditReqDTO.getTitle());
        house.setRentType(houseAddOrEditReqDTO.getRentType());
        house.setFloor(houseAddOrEditReqDTO.getFloor());
        house.setAllFloor(houseAddOrEditReqDTO.getAllFloor());
        house.setHouseType(houseAddOrEditReqDTO.getHouseType());
        house.setRooms(houseAddOrEditReqDTO.getRooms());
        house.setPosition(houseAddOrEditReqDTO.getPosition());
        house.setArea(BigDecimal.valueOf(houseAddOrEditReqDTO.getArea()));
        house.setPrice(BigDecimal.valueOf(houseAddOrEditReqDTO.getPrice()));
        house.setIntro(houseAddOrEditReqDTO.getIntro());
        house.setDevices(
                houseAddOrEditReqDTO.getDevices()
                        .stream().filter(s -> !s.isEmpty())
                        .collect(Collectors.joining(","))
        );
        house.setHeadImage(houseAddOrEditReqDTO.getHeadImage());
        //转json
        house.setImages(JsonUtil.obj2String(houseAddOrEditReqDTO.getImages()));
        house.setCityId(houseAddOrEditReqDTO.getCityId());
        house.setCityName(houseAddOrEditReqDTO.getCityName());
        house.setRegionId(houseAddOrEditReqDTO.getRegionId());
        house.setRegionName(houseAddOrEditReqDTO.getRegionName());
        house.setCommunityName(houseAddOrEditReqDTO.getCommunityName());
        house.setDetailAddress(houseAddOrEditReqDTO.getDetailAddress());
        house.setLongitude(BigDecimal.valueOf(houseAddOrEditReqDTO.getLongitude()));
        house.setLatitude(BigDecimal.valueOf(houseAddOrEditReqDTO.getLatitude()));

        //编辑 判断是否更新 城市房源映射 标签房源映射
        //进而编辑redis和mysql
        if(null != houseAddOrEditReqDTO.getHouseId()){
            house.setId(houseAddOrEditReqDTO.getHouseId());
            //判断是否修改映射
            House existHouse = houseMapper.selectById(houseAddOrEditReqDTO.getCityId());
            if(cityHouseNeedChange(existHouse, houseAddOrEditReqDTO.getCityId())){
                editCityHouses(houseAddOrEditReqDTO.getHouseId(),
                        existHouse.getCityId(),
                        houseAddOrEditReqDTO.getCityId(),
                        houseAddOrEditReqDTO.getCityName());
            }
            List<TagHouse> tagHouses = tagHouseMapper.selectList(
                    new LambdaQueryWrapper<TagHouse>()
                            .eq(TagHouse::getHouseId, houseAddOrEditReqDTO.getHouseId()));
            if(tagHouseNeedChange(tagHouses, houseAddOrEditReqDTO.getTagCodes())){
                editTagHouses(houseAddOrEditReqDTO.getHouseId(),
                        tagHouses, houseAddOrEditReqDTO.getTagCodes());
            }
        }
        //如果是新增
        houseMapper.insertOrUpdate(house);

        //新增
        if(null == houseAddOrEditReqDTO.getHouseId()){
            HouseStatus houseStatus = new HouseStatus();
            houseStatus.setHouseId(house.getId());
            houseStatus.setStatus(HouseStatusEnum.UP.name());
            houseStatusMapper.insert(houseStatus);

            // MySQL, Redis
            addCityHouse(house.getId(), house.getCityId(), house.getCityName());

            // MySQL
            addTagHouses(house.getId(), houseAddOrEditReqDTO.getTagCodes());
        }

        //缓存房源完整信息
        cacheHouse(house.getId());
        return house.getId();
    }

    /**
     * 房源详情
     * @param houseId 房源ID
     * @return 房源详情
     */
    @Override
    public HouseDTO detail(Long houseId) {

        //id<0解决缓存穿透
        if(null == houseId || houseId < 0){
            log.warn("要查询的房源id为空");
            return null;
        }
        //查询房源详情缓存
        HouseDTO houseDTO = getCacheHouse(houseId);
        //判断缓存是否存在
        if(null != houseDTO){
            return houseDTO;
        }
        //缓存不存在查mysql
        houseDTO = getHouseDTObyId(houseId);
        //mysql不存在，缓存空对象
        if(null == houseDTO){
            cacheNullHouse(houseId, 60L);
            log.warn("要查询的房源不存在");
            return null;
        }
        //mysql存在，缓存对象
        cacheHouse(houseDTO);
        return houseDTO;
    }

    @Override
    public void editStatus(HouseStatusEditReqDTO houseStatusEditReqDTO) {
        //先校验房源是否存在
        House house = houseMapper.selectById(houseStatusEditReqDTO.getHouseId());
        if(null == house){
            throw new ServiceException("房源不存在，无法修改状态");
        }
        //在校验房源必须有状态
        HouseStatus houseStatus = houseStatusMapper.selectOne(
                new LambdaQueryWrapper<HouseStatus>().eq(HouseStatus::getHouseId, house.getId())
        );
        if(null == houseStatus || StringUtils.isEmpty(houseStatus.getStatus())){
            throw new ServiceException("房源没有状态，无法修改状态");
        }

        //枚举状态传参

        HouseStatusEnum houseStatusEnum = HouseStatusEnum.getByName(houseStatusEditReqDTO.getStatus());
        if(null == houseStatusEnum){
            throw new ServiceException("要修改的房源状态有误");
        }
        //更新数据库，还要判断是否传来出租时常码
        houseStatus.setStatus(houseStatusEditReqDTO.getStatus());
        if(HouseStatusEnum.RENTING.name().equalsIgnoreCase(houseStatusEditReqDTO.getStatus())){
            if(StringUtils.isEmpty(houseStatusEditReqDTO.getRentTimeCode())){
                throw new ServiceException("要修改的房源状态为出租中，必须传出租时长码,无法修改状态");
            }
            houseStatus.setRentTimeCode(houseStatusEditReqDTO.getRentTimeCode());
            houseStatus.setRentStartTime(TimestampUtil.getCurrentMillis());
            switch (houseStatusEditReqDTO.getRentTimeCode()){
                case "one_year" -> houseStatus.setRentEndTime(TimestampUtil.getYearLaterMillis(1L));
                case "half_year" -> houseStatus.setRentEndTime(TimestampUtil.getMonthsLaterMillis(6L));
                case "thirty_seconds" -> houseStatus.setRentEndTime(TimestampUtil.getSecondsLaterMillis(30L));
                default -> throw new ServiceException("出租时长错误，无法修改状态");
            }
        }
        houseStatusMapper.updateById(houseStatus);

    }

    /**
     * 搜索房源列表，支持翻页筛选
     * @param houseListReqDTO 搜索参数
     * @return 响应结果
     */
    @Override
    public BasePageDTO<HouseDescDTO> list(HouseListReqDTO houseListReqDTO) {
        BasePageDTO<HouseDescDTO> result = new BasePageDTO<>();

        Long totals = houseMapper.selectCountWithStatus(houseListReqDTO);
        if(0 == totals){
            result.setTotals(0);
            result.setTotalPages(0);
            result.setList(Arrays.asList());
            log.info("查询的房源列表为空！HouseListReqDTO:{}", JsonUtil.obj2String(houseListReqDTO));
            return result;
        }

        //查询列表
        List<HouseDescDTO> houses = houseMapper.selectPageWithStatus(houseListReqDTO);
        result.setTotals(Integer.parseInt(String.valueOf(totals)));
        result.setTotalPages(BasePageDTO.calculateTotalPages(totals, houseListReqDTO.getPageSize()));
        if(CollectionUtils.isEmpty(houses)){
            log.info("超出查询房源列表范围！HouseListReqDTO:{}", JsonUtil.obj2String(houseListReqDTO));
            result.setList(Arrays.asList());
            return result;
        }
        result.setList(houses);
        return result;
    }

    @Override
    public List<Long> listByUserId(Long userId) {
        if(null == userId){
            return Arrays.asList();
        }
        List<House> houses = houseMapper.selectList(
                new LambdaQueryWrapper<House>()
                        .eq(House::getUserId, userId)
        );
        return houses.stream().map(House::getId)
                .distinct()
                .collect(Collectors.toList());
    }

    @Override
    public void refreshHouseIds() {
        //查询全量城市列表(2级城市)
        List<SysRegion> sysRegions = regionMapper.selectList(
                new LambdaQueryWrapper<SysRegion>()
                        .eq(SysRegion::getLevel, 2)
        );
        for(SysRegion sysRegion: sysRegions){
            //删除当前城市下所有的房源列表redis
            Long cityId = sysRegion.getId();
            redisService.removeForAllList(CITY_HOUSE_PREFIX + cityId);

            //查询当前城市下所有的房源列表mysql
            List<CityHouse> cityHouses = cityHouseMapper.selectList(
                    new LambdaQueryWrapper<CityHouse>()
                            .eq(CityHouse::getCityId, cityId)
            );

            //新增当前城市下所有的房源列表映射redis
            if(!CollectionUtils.isEmpty(cityHouses)){
                redisService.setCacheList(CITY_HOUSE_PREFIX + cityId,
                        cityHouses.stream()
                                .map(CityHouse::getHouseId).distinct()
                                .collect(Collectors.toList()));
            }
            //更新房源列表详细信息(redis)
            for(CityHouse cityHouse : cityHouses){
                cacheHouse(cityHouse.getHouseId());
            }
        }
    }

    /**
     * 从缓存获取房源详情
     * @param houseId 房源ID
     * @return 房源详情
     */
    private HouseDTO getCacheHouse(Long houseId) {
        if(null == houseId){
            return null;
        }
        HouseDTO houseDTO = null;
        try {
            String houseDTODtr = redisService.getCacheObject(HOUSE_PREFIX + houseId, String.class);
            if(StringUtils.isBlank(houseDTODtr)){
                return null;
            }
            houseDTO = JsonUtil.string2Obj(houseDTODtr, HouseDTO.class);
        } catch (Exception e) {
            log.error("从缓存获取房源详情失败,key:{}", HOUSE_PREFIX+houseId, e);
        }
        return houseDTO;
    }

    /**
     * 缓存房源完整信息
     * @param id
     */
    @Override
    public void cacheHouse(Long id) {
        if(null == id){
            log.warn("缓存房源完整信息失败,参数为空");
            return;
        }

        HouseDTO houseDTO = getHouseDTObyId(id);
        if(null == houseDTO){
            log.warn("缓存房源完整信息失败,参数为空");
            return;
        }
        cacheHouse(houseDTO);
    }


    /**
     * 缓存房源完整数据
     * @param houseDTO
     */
    private void cacheHouse(HouseDTO houseDTO){
        if(null == houseDTO){
            log.warn("缓存房源完整数据失败,参数为空");
            return;
        }

        try {
            redisService.setCacheObject(HOUSE_PREFIX + houseDTO.getHouseId(),
                    JsonUtil.obj2String(houseDTO));
        } catch (Exception e) {
            log.error("缓存房源完整数据失败,house:{}", JsonUtil.obj2String(houseDTO), e);
            //对于房源完整性信息，是否存在于redis，不需要强一致性，C端查询时，redis没有可以查mysql再放入redis
        }

    }

    /**
     * 缓存房源完整数据,带过期时间
     * @param houseDTO
     */
    private void cacheHouse(HouseDTO houseDTO, Long timeout){
        if(null == houseDTO){
            log.warn("缓存房源完整数据失败,参数为空");
            return;
        }

        try {
            redisService.setCacheObject(HOUSE_PREFIX + houseDTO.getHouseId(),
                    JsonUtil.obj2String(houseDTO), timeout, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("缓存房源完整数据失败,house:{}", JsonUtil.obj2String(houseDTO), e);
            //对于房源完整性信息，是否存在于redis，不需要强一致性，C端查询时，redis没有可以查mysql再放入redis
        }

    }

    private void cacheNullHouse(Long id, Long timeout) {
        if(null == id){
            log.warn("缓存房源参数id为空");
            return;
        }

        try {
            redisService.setCacheObject(
                    HOUSE_PREFIX + id,
                    JsonUtil.obj2String(new HouseDTO()),
                    timeout,
                    TimeUnit.SECONDS
            );
        } catch (Exception e) {
            log.error("缓存房源完整数据失败,house:{}", JsonUtil.obj2String(id), e);
            //对于房源完整性信息，是否存在于redis，不需要强一致性，C端查询时，redis没有可以查mysql再放入redis
        }
    }

    /**
     * 获取房源完整数据
     * @param id
     * @return
     */
    private HouseDTO getHouseDTObyId(Long id) {
        if(null == id){
            log.warn("获取房源完整数据失败,id参数为空");
            return null;
        }

        House house = houseMapper.selectById(id);
        if(null == house){
            log.error("获取房源完整数据失败,houseId:{}", id);
            return null;
        }
        AppUser appUser = appUserMapper.selectById(house.getUserId());
        if(null == appUser){
            log.error("获取房源完整数据失败,房东信息不存在,userId:{}", house.getUserId());
            return null;
        }
        HouseStatus  houseStatus = houseStatusMapper.selectOne(
                new LambdaQueryWrapper<HouseStatus>()
                        .eq(HouseStatus::getHouseId, id)
        );
        if(null == houseStatus){
            log.error("获取房源完整数据失败,房源状态不存在,houseId:{}", id);
            return null;
        }
        List<TagHouse> tagHouses = tagHouseMapper.selectList(
                new LambdaQueryWrapper<TagHouse>()
                        .eq(TagHouse::getHouseId, id)
        );
        
        //将完整信息组装
        return convertToHouseDTO(house, appUser, houseStatus, tagHouses);
    }

    /**
     * 将完整信息组装
     * @param house
     * @param appUser
     * @param houseStatus
     * @param tagHouses
     * @return
     */
    private HouseDTO convertToHouseDTO(House house, AppUser appUser, HouseStatus houseStatus, List<TagHouse> tagHouses) {
        if(null == house || null == appUser || null == houseStatus){
            log.warn("房源信息不完整！");
            return null;
        }
        HouseDTO houseDTO = new HouseDTO();
        BeanUtils.copyProperties(house, houseDTO);
        BeanUtils.copyProperties(appUser, houseDTO);
        BeanUtils.copyProperties(houseStatus, houseDTO);

        houseDTO.setArea(house.getArea().doubleValue());
        houseDTO.setPrice(house.getPrice().doubleValue());
        houseDTO.setLongitude(house.getLongitude().doubleValue());
        houseDTO.setLatitude(house.getLatitude().doubleValue());
        houseDTO.setImages(JsonUtil.string2List(house.getImages(), String.class));

        List<String> datakeys = Arrays.stream(house.getDevices().split(","))
                .distinct()
                .collect(Collectors.toList());
        List<DictionaryDataDTO> deviceDataDTOS = sysDictionaryService.getDicDataByKeys(datakeys);
        List<DeviceDTO> deviceDTOS = deviceDataDTOS.stream()
                .map(dataDTO -> {
                    DeviceDTO deviceDTO = new DeviceDTO();
                    deviceDTO.setDeviceCode(dataDTO.getDataKey());
                    deviceDTO.setDeviceName(dataDTO.getValue());
                    return deviceDTO;
                }).collect(Collectors.toList());
        houseDTO.setDevices(deviceDTOS);

        List<String> tagCodes = tagHouses.stream()
                .map(TagHouse::getTagCode)
                .distinct()
                .collect(Collectors.toList());
        if(CollectionUtils.isEmpty(tagCodes)){
            List<Tag> tags = tagMapper.selectList(
                    new LambdaQueryWrapper<Tag>()
                            .in(Tag::getTagCode, tagCodes)
            );
            houseDTO.setTags(BeanCopyUtil.copyListProperties(tags, TagDTO::new));
        }
        return houseDTO;
    }

    /**
     * 新增标签房源映射
     * @param id
     * @param tagCodes
     */
    private void addTagHouses(Long id, List<String> tagCodes) {
        List<TagHouse> tagHouses = tagCodes.stream()
                .map(tagCode -> {
                    TagHouse tagHouse = new TagHouse();
                    tagHouse.setTagCode(tagCode);
                    tagHouse.setHouseId(id);
                    return tagHouse;
                }).collect(Collectors.toList());
        tagHouseMapper.insert(tagHouses);
    }

    /**
     * 编辑标签房源映射
     * @param houseId
     * @param oldTagHouses
     * @param newTagCodes
     */
    private void editTagHouses(Long houseId, List<TagHouse> oldTagHouses, List<String> newTagCodes) {
        //先过滤出需要删除的标签吗
        Set<String> oldTagCodes = oldTagHouses.stream()
                .map(TagHouse::getTagCode)
                .collect(Collectors.toSet());
        List<String> deleteTagCodes = oldTagCodes.stream()
                .filter(tagCode -> !newTagCodes.contains(tagCode))
                .collect(Collectors.toList());

        //删除需要删除的
        if(!CollectionUtils.isEmpty(deleteTagCodes)){
            tagHouseMapper.delete(new LambdaQueryWrapper<TagHouse>()
                    .eq(TagHouse::getHouseId,houseId)
                    .in(TagHouse::getTagCode, deleteTagCodes));
        }
        //过滤出需要新增的
        List<TagHouse> newTagHouses = newTagCodes.stream()
                .filter(tagCode -> !oldTagCodes.contains(tagCode))
                .map(tagCode -> {
                    TagHouse tagHouse = new TagHouse();
                    tagHouse.setTagCode(tagCode);
                    tagHouse.setHouseId(houseId);
                    return tagHouse;
                }).collect(Collectors.toList());

        if(!CollectionUtils.isEmpty(newTagHouses)){
            tagHouseMapper.insert(newTagHouses);
        }
    }

    /**
     * 新增城市房源映射
     * @param id
     * @param cityId
     * @param cityName
     */
    private void addCityHouse(Long id, Long cityId, String cityName) {
        CityHouse cityHouse = new CityHouse();
        cityHouse.setCityId(cityId);
        cityHouse.setCityName(cityName);
        cityHouse.setHouseId(id);
        cityHouseMapper.insert(cityHouse);

        cacheCityHouses(1, id, null, cityId);
    }

    /**
     * 编辑城市房源映射
     * @param houseId
     * @param oldCityId
     * @param newCityName
     * @param newCityId
     */
    private void editCityHouses(Long houseId, Long oldCityId, Long newCityId, String newCityName) {
        cityHouseMapper.delete(
                new LambdaQueryWrapper<CityHouse>()
                        .eq(CityHouse::getCityId, oldCityId)
                        .eq(CityHouse::getHouseId, houseId)

        );

        CityHouse cityHouse = new CityHouse();
        cityHouse.setCityId(houseId);
        cityHouse.setCityName(newCityName);
        cityHouse.setHouseId(newCityId);
        cityHouseMapper.insert(cityHouse);

        //更新缓存
        cacheCityHouses(2, houseId, oldCityId, newCityId);
    }

    /**
     * 缓存城市房源映射关系
     * @param houseId
     * @param oldCityId
     * @param newCityId
     * @param i
     */
    private void cacheCityHouses(int i, Long houseId, Long oldCityId, Long newCityId) {
        try {
            if(1 == i){
                //新增场景
                redisService.setCacheList(CITY_HOUSE_PREFIX + newCityId, Arrays.asList(houseId));
            }else if(2 == i){
                //修改场景
                //删除老房源
                redisService.removeForList(CITY_HOUSE_PREFIX + oldCityId, houseId);
                //增加新房源
                redisService.setCacheList(CITY_HOUSE_PREFIX + newCityId, Arrays.asList(houseId));
            }else{
                log.error("无效操作，缓存城市房源映射关系相关信息");
            }
        } catch (Exception e) {
            log.error("缓存城市房源映射关系异常, i:{}, houseId:{}, oldCityId:{}, newCityId:{}",
                    i, houseId, oldCityId, newCityId);
            throw e;
        }
    }

    /**
     * 判断标签房源映射是否需要更新
     * @param oldTagHouses
     * @param newTagCodes
     * @return
     */
    private boolean tagHouseNeedChange(List<TagHouse> oldTagHouses, List<String> newTagCodes) {
        List<String> oldTagCodes = oldTagHouses.stream()
                .map(TagHouse::getTagCode)
                .sorted()
                .collect(Collectors.toList());
        newTagCodes = newTagCodes.stream()
                .sorted()
                .collect(Collectors.toList());
        return !Objects.equals(oldTagCodes, newTagCodes);
    }


    /**
     * 判断城市房源映射是否需要更新
     * @param existHouse
     * @param cityId
     * @return
     */
    private boolean cityHouseNeedChange(House existHouse, Long cityId) {
        return !existHouse.getCityId().equals(cityId);
    }

    /**
     * 校验新增或编辑房源参数
     * @param houseAddOrEditReqDTO 新增或编辑房源参数
     */
    private void checkAddOrEditReq(HouseAddOrEditReqDTO houseAddOrEditReqDTO) {
        //检验房东信息
        AppUser appUser = appUserMapper.selectById(houseAddOrEditReqDTO.getUserId());
        if(null == appUser){
            throw new ServiceException("房东信息不存在", ResultCode.INVALID_PARA.getCode());
        }

        //检验城市信息
        List<Long> regionIds = Arrays.asList(
                houseAddOrEditReqDTO.getCityId(), houseAddOrEditReqDTO.getRegionId()
        );
        List<SysRegion> regions = regionMapper.selectBatchIds(regionIds);
        if(regionIds.size() != regions.size()){
            throw new ServiceException("传递的城市信息有误！", ResultCode.INVALID_PARA.getCode());
        }

        //验证标签码
        LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<Tag>()
                .in(Tag::getTagCode, houseAddOrEditReqDTO.getTagCodes());
        List<Tag> tags = tagMapper.selectList(queryWrapper);
        if(houseAddOrEditReqDTO.getTagCodes().size() != tags.size()){
            throw new ServiceException("传递的标签信息有误！", ResultCode.INVALID_PARA.getCode());
        }
    }

    //定时任务实现扭转房源状态
    //每日零点开始执行定时任务
    @Scheduled(cron = "0 0 0 * * ?")
    public void scheduledHouseStatus(){
        log.info("开始执行房源状态扭转任务");

        //加redis分布式锁，锁的value来判断所是否为当前线程所有
        String value = UUID.randomUUID().toString();
        try {
            Boolean lock = redisService.setCacheObjectIfAbsent(LOCK_KEY, value, 180L, TimeUnit.SECONDS);
            if(Boolean.TRUE.equals( lock)){
                //查询全部已出租房源
                List<HouseStatus> rentHouseStatusList = houseStatusMapper.selectList(
                        new LambdaQueryWrapper<HouseStatus>()
                                .eq(HouseStatus::getStatus, HouseStatusEnum.RENTING.name())
                );
                //过滤需要扭转状态的房源列表(出租到期时间)
                List<HouseStatus> needChangeHouseStatusList = rentHouseStatusList.stream()
                        .filter(houseStatus -> null != houseStatus.getRentEndTime()
                            && 0 > TimestampUtil.calculateDifferenceMillis(
                                TimestampUtil.getCurrentSeconds(), houseStatus.getRentEndTime()))
                        .collect(Collectors.toList());
                //批量更新房源状态
                for (HouseStatus housestatus:needChangeHouseStatusList) {
                    HouseStatusEditReqDTO houseStatusEditReqDTO = new HouseStatusEditReqDTO();
                    housestatus.setHouseId(housestatus.getHouseId());
                    houseStatusEditReqDTO.setStatus(HouseStatusEnum.UP.name());
                    editStatus(houseStatusEditReqDTO);

                }
            }else{
                //获取锁失败，跳过执行
                log.info("定时任务被其他实例执行");
            }
        } finally {
            //解锁，只能解锁自己
            redisService.cad(LOCK_KEY, value);
        }
    }

    @Scheduled(cron = "0 0 0 * * ?")
    public void scheduledHouseStatus2(){
        log.info("开始执行房源状态扭转任务");

        //加redisson分布式锁
        RLock lock = redissonLockService.acquire(LOCK_KEY, -1);
        if(null == lock){
            log.info("定时任务被其他实例执行");
            return;
        }

        try {
            //查询全部已出租房源
            List<HouseStatus> rentHouseStatusList = houseStatusMapper.selectList(
                    new LambdaQueryWrapper<HouseStatus>()
                            .eq(HouseStatus::getStatus, HouseStatusEnum.RENTING.name())
            );
            //过滤需要扭转状态的房源列表(出租到期时间)
            List<HouseStatus> needChangeHouseStatusList = rentHouseStatusList.stream()
                    .filter(houseStatus -> null != houseStatus.getRentEndTime()
                            && 0 > TimestampUtil.calculateDifferenceMillis(
                            TimestampUtil.getCurrentSeconds(), houseStatus.getRentEndTime()))
                    .collect(Collectors.toList());
            //批量更新房源状态
            for (HouseStatus housestatus:needChangeHouseStatusList) {
                HouseStatusEditReqDTO houseStatusEditReqDTO = new HouseStatusEditReqDTO();
                housestatus.setHouseId(housestatus.getHouseId());
                houseStatusEditReqDTO.setStatus(HouseStatusEnum.UP.name());
                editStatus(houseStatusEditReqDTO);

            }
        } finally {
            if(lock.isLocked() && lock.isHeldByCurrentThread()){
                redissonLockService.releaseLock(lock);
            }
        }
    }

    @Override
    public BasePageDTO<HouseDTO> searchList(SearchHouseListReqDTO searchHouseListReqDTO) {
        //获取城市全量房源信息列表
        List<HouseDTO> houseDTOList = getCacheHouseListByCity(searchHouseListReqDTO.getCityId());
        //筛选排序分页
        return filterHouse(houseDTOList, searchHouseListReqDTO);
    }

    //策略模式+工厂模式:实现
    private BasePageDTO<HouseDTO> filterHouse(List<HouseDTO> houseDTOList, SearchHouseListReqDTO searchHouseListReqDTO) {
        //筛选 多策略都要过一遍
        List<HouseDTO> validHouseDTOList = houseFilter(houseDTOList, searchHouseListReqDTO);
        //排序 多策略 制定一个即可
        validHouseDTOList = houseSorting(validHouseDTOList, searchHouseListReqDTO);
        //分页
        return housePage(validHouseDTOList, searchHouseListReqDTO);
    }

    private List<HouseDTO> houseFilter(List<HouseDTO> houseDTOList, SearchHouseListReqDTO searchHouseListReqDTO) {
        return houseDTOList.stream()
                .filter(houseDTO -> houseFilterMap.values().stream()
                        .allMatch(houseFilter ->{
                            try {
                                return houseFilter.filter(houseDTO, searchHouseListReqDTO);
                            } catch (Exception e) {
                                log.error("筛选异常，houseDTO:{}, filter:{}", JsonUtil.obj2String(houseDTO), houseFilter.getClass().getName(), e);
                                return false;
                            }
                        })).collect(Collectors.toList());
    }

    private List<HouseDTO> houseSorting(List<HouseDTO> validHouseDTOList, SearchHouseListReqDTO searchHouseListReqDTO) {
        ISortStrategy sortStrategy = SortStrategyFactory.getSortStrategy(searchHouseListReqDTO.getSort());
        return sortStrategy.sort(validHouseDTOList, searchHouseListReqDTO);
    }


    private BasePageDTO<HouseDTO> housePage(List<HouseDTO> validHouseDTOList, SearchHouseListReqDTO searchHouseListReqDTO) {
        List<HouseDTO> pageHouseDTOList = validHouseDTOList.stream()
                .skip(searchHouseListReqDTO.getOffset())
                .limit(searchHouseListReqDTO.getPageSize())
                .collect(Collectors.toList());
        BasePageDTO<HouseDTO> result = new BasePageDTO<>();
        result.setTotals(validHouseDTOList.size());
        result.setTotalPages(BasePageDTO.calculateTotalPages(pageHouseDTOList.size(), searchHouseListReqDTO.getPageSize()));
        result.setList(pageHouseDTOList);
        return result;
    }

    //基础写法
    private BasePageDTO<HouseDTO> filterHouseV1(List<HouseDTO> houseDTOList, SearchHouseListReqDTO reqDTO){
        //筛选
        if(null != reqDTO.getRegionId()){
            houseDTOList = houseDTOList.stream()
                    .filter(houseDTO -> houseDTO.getRegionId().equals(reqDTO.getRegionId()))
                    .collect(Collectors.toList());
        }
        if(!CollectionUtils.isEmpty(reqDTO.getRentTypes())){
            houseDTOList = houseDTOList.stream()
                    .filter(houseDTO -> reqDTO.getRentTypes().contains(houseDTO.getRentType()))
                    .collect(Collectors.toList());
        }
        if(!CollectionUtils.isEmpty(reqDTO.getRooms())){
            houseDTOList = houseDTOList.stream()
                    .filter(houseDTO -> reqDTO.getRooms().contains(houseDTO.getRooms()))
                    .collect(Collectors.toList());
        }
        if(!CollectionUtils.isEmpty(reqDTO.getRentalRanges())){
            houseDTOList = houseDTOList.stream()
                    .filter(houseDTO -> filterHouseByRentalRanges(houseDTO.getPrice(), reqDTO.getRentalRanges()))
                    .collect(Collectors.toList());
        }
        //筛选状态
        houseDTOList = houseDTOList.stream()
                .filter(houseDTO -> houseDTO.getStatus().equalsIgnoreCase(HouseStatusEnum.UP.name()))
                .collect(Collectors.toList());

        //排序
        if(StringUtils.isNotEmpty(reqDTO.getSort())){
            if(reqDTO.getSort().equalsIgnoreCase(HouseSortEnum.DISTANCE.name())){
                houseDTOList = houseDTOList.stream()
                        .sorted(Comparator.comparingDouble(
                                houseDTO -> houseDTO.calculateDistance(
                                        reqDTO.getLongitude(), reqDTO.getLatitude()
                                )
                        )).collect(Collectors.toList());
            }else if(reqDTO.getSort().equalsIgnoreCase(HouseSortEnum.PRICE_ASC.name())){
                houseDTOList = houseDTOList.stream()
                        .sorted(Comparator.comparingDouble(HouseDTO::getPrice))
                        .collect(Collectors.toList());
            }else if(reqDTO.getSort().equalsIgnoreCase(HouseSortEnum.PRICE_DESC.name())){
                houseDTOList = houseDTOList.stream()
                        .sorted(Comparator.comparingDouble(HouseDTO::getPrice).reversed())
                        .collect(Collectors.toList());
            }else{
                log.error("不存在的排序规则，将按照默认的距离排序");
                houseDTOList = houseDTOList.stream()
                        .sorted(Comparator.comparingDouble(
                                houseDTO -> houseDTO.calculateDistance(
                                        reqDTO.getLongitude(), reqDTO.getLatitude()
                                )
                        )).collect(Collectors.toList());
            }
        }

        //翻页,获取分页后的列表
        List<HouseDTO> pageHouseDTOList = houseDTOList.stream()
                .skip(reqDTO.getOffset())
                .limit(reqDTO.getPageSize())
                .collect(Collectors.toList());
        //计算总数和总页数
        int totalCount = houseDTOList.size();
        int totalPages = BasePageDTO.calculateTotalPages(totalCount, reqDTO.getPageSize());
        //创建BasePageDTO对象并设置值
        BasePageDTO<HouseDTO> pageDTO = new BasePageDTO<>();
        pageDTO.setTotals(totalCount);
        pageDTO.setTotalPages(totalPages);
        pageDTO.setList(pageHouseDTOList);
        return pageDTO;
    }

    /**
     * 根据价格区间筛选房源
     * @param price
     * @param rentalRanges
     * @return
     */
    private boolean filterHouseByRentalRanges(Double price, List<String> rentalRanges) {
        if(null == price){
            return false;
        }
        boolean isPriceInRange = false;
        for (String rentalRange : rentalRanges) {
            // 1800
            // [range_1, range_3]
            switch (rentalRange) {
                case "range_1":
                    isPriceInRange = price < 1000;
                    break;
                case "range_2":
                    isPriceInRange = price >= 1000 && price < 1500;
                    break;
                case "range_3":
                    isPriceInRange = price >= 1500 && price < 2000;
                    break;
                case "range_4":
                    isPriceInRange = price >= 2000 && price < 3000;
                    break;
                case "range_5":
                    isPriceInRange = price >= 3000 && price < 5000;
                    break;
                case "range_6":
                    isPriceInRange = price >= 5000;
                    break;
                default:
                    log.error("超出资金筛选范围, rentalRange:{}", rentalRange);
                    break;
            }
            if (isPriceInRange) {
                return true;
            }
        }
        return false;
    }


    /**
     * 根据redis中的城市id获取城市下的房源列表详细信息
     * @param cityId
     * @return
     */
    private List<HouseDTO> getCacheHouseListByCity(Long cityId) {
        if(null == cityId){
            return Arrays.asList();
        }
        List<HouseDTO> resultList = new ArrayList<>();
        //先从缓存中获取房源id列表
        List<Long> houseIds = getCacheCityHouses(cityId);
        //获取房源详细信息
        Set<Long> houseIdSet = new HashSet<>(houseIds);
        for (Long houseID : houseIdSet){
            HouseDTO houseDTO = detail(houseID);
            if(null != houseDTO){
                resultList.add(houseDTO);
            }
        }
        return resultList;

    }

    /**
     * 获取城市下的房源列表id
     * @param cityId
     * @return
     */
    private List<Long> getCacheCityHouses(Long cityId) {
        if(null == cityId){
            return Arrays.asList();
        }
        List<Long> houseIds = new ArrayList<>();

        try {
            houseIds = redisService.getCacheList(CITY_HOUSE_PREFIX + cityId, Long.class);
        } catch (Exception e) {
            log.error("从缓存中获取城市下的房源列表异常，key:{}",CITY_HOUSE_PREFIX + cityId, e);
        }
        return houseIds;
    }

}
